package main

// [net/ used CGO]---> "CGO_ENABLED=0 go build ./"
import (
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"sort"
	"strings"
)

const (
	_IMG_FILE         = int8(0)
	_DIR_FILE         = int8(1)
	_OTHER_FILE       = int8(2)
	_MAX_SUB_FILE_NUM = 2000
)

var (
	_SUPPORTED_IMAGE_FILE_SUFFIXES = []string{".png", ".jpg"}
	_RootTemplate                  = parseTemplateText("RootTemplate", RootTemplate)
	_ImgListTemplate               = parseTemplateText("ImgListTemplate", ImgListTemplate)
)

type _FileType int8

type FileItem struct {
	URL      string
	Name     string
	FileType int8
}

type TemArgList struct {
	CurDir     string
	DirItems   []FileItem
	ImgItems   []FileItem
	OtherItems []FileItem
}

type FileInfoSlice []os.FileInfo

func (s FileInfoSlice) Len() int { return len(s) }

func (s FileInfoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

func (s FileInfoSlice) Less(i, j int) bool { return s[i].Name() < s[j].Name() }

func main() {
	fmt.Print(BITO_HEAD)

	ip, port, dir := flagValues()
	var addr = fmt.Sprintf("%s:%s", ip, port)

	mux := http.NewServeMux()
	mux.HandleFunc("/", rootHandleFunc)

	// //注意,这样写是错的, http.FileServer(http.Dir(dir)) 正常情况下之能映射到根路由"/"
	// http.Handle("/files", http.FileServer(http.Dir(dir)))

	// 注意这里 mux.Handle("/files/", ...) 的 "/files/" 不能写成 "/files"
	// mux.Handle("/files", http.StripPrefix("/files/", http.FileServer(http.Dir(dir))))
	mux.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir(dir))))
	mux.HandleFunc("/list", genImgListHandleFunc(dir))
	fmt.Println("----------------------------------------------")
	fmt.Printf("serve at %s\n", addr)
	fmt.Printf("max file number : %d\n", _MAX_SUB_FILE_NUM)
	fmt.Println("----------------------------------------------")
	err := http.ListenAndServe(addr, mux)
	if err != nil {
		log.Fatal(err)
	}
}

func flagValues() (string, string, string) {
	var ip *string = flag.String("ip", "0.0.0.0", "服务器ip")
	var port *string = flag.String("port", "8888", "服务器port")
	var dir *string = flag.String("dir", "./", "图片目录")
	flag.Parse()
	return *ip, *port, *dir
}

func requestLog(r *http.Request, ex_info string) {
	//=========================================================
	//打印日志
	remoteAddr := r.RemoteAddr
	var logString string
	if ex_info == "" {
		logString = fmt.Sprintf("request from %s", remoteAddr)
	} else {
		logString = fmt.Sprintf("request from %s : %s", remoteAddr, ex_info)
	}
	// // format string must set to this date time:
	// //     Mon Jan 2 15:04:05 -0700 MST 2006
	// curTimeString := time.Now().Format("2006/1/2 15:04:05")
	log.Println(logString)
}

func rootHandleFunc(w http.ResponseWriter, r *http.Request) {
	requestLog(r, "rootHandleFunc")
	thisHost := r.Host
	tem := _RootTemplate
	if err := tem.Execute(w, thisHost); err != nil {
		log.Println(err, "template execution error.")
		http.Error(w, "template execution error", http.StatusBadRequest)
		return
	}
}

func genImgListHandleFunc(baseDirPath string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		requestLog(r, fmt.Sprintf("imgListHandleFunc: [row query]: %s", r.URL.RawQuery))
		thisHost := r.Host
		//=========================================================
		//获取Get请求的参数
		query := r.URL.Query()
		curDirRelativePath := query.Get("curDir")
		if containsDotDot(curDirRelativePath) {
			log.Println("invalid URL path (contains '..')")
			http.Error(w, "invalid URL path", http.StatusBadRequest)
			return
		}
		curDirPath := baseDirPath + "/" + curDirRelativePath
		curDirFileInfo, err := os.Stat(curDirPath)
		if err != nil || !curDirFileInfo.IsDir() {
			log.Println(err, "invalid curDir (curDir is not directory)")
			http.Error(w, "invalid curDir", http.StatusBadRequest)
			return
		}
		// curDir, err := http.Dir(curDirPath).Open()
		curDir, err := os.Open(curDirPath)
		if err != nil {
			log.Println(err, "open curDir error")
			http.Error(w, "open curDir error", http.StatusBadRequest)
			return
		}

		subFileInfoList, err := curDir.Readdir(_MAX_SUB_FILE_NUM)
		if err != nil {
			log.Println(err, "list curDir error")
			http.Error(w, "list curDir error", http.StatusBadRequest)
			return
		}
		//=========================================================
		//按文件名排序
		sort.Sort(FileInfoSlice(subFileInfoList))
		//=========================================================
		tem := _ImgListTemplate

		temArgs := TemArgList{
			CurDir:     curDirRelativePath,
			DirItems:   make([]FileItem, 0),
			ImgItems:   make([]FileItem, 0),
			OtherItems: make([]FileItem, 0),
		}

		for i := 0; i < len(subFileInfoList); i++ {
			f := subFileInfoList[i]
			if f.IsDir() {
				fileItem := FileItem{
					URL:      "http://" + thisHost + "/list?curDir=" + curDirRelativePath + "/" + f.Name(),
					Name:     f.Name(),
					FileType: _DIR_FILE,
				}
				temArgs.DirItems = append(temArgs.DirItems, fileItem)
			} else if isImgFileName(f.Name()) {
				fileItem := FileItem{
					URL:      "http://" + thisHost + "/files/" + curDirRelativePath + "/" + f.Name(),
					Name:     f.Name(),
					FileType: _IMG_FILE,
				}
				temArgs.ImgItems = append(temArgs.ImgItems, fileItem)
			} else {
				fileItem := FileItem{
					URL:      "http://" + thisHost + "/files/" + curDirRelativePath + "/" + f.Name(),
					Name:     f.Name(),
					FileType: _IMG_FILE,
				}
				temArgs.OtherItems = append(temArgs.OtherItems, fileItem)
			}
		}
		if err := tem.Execute(w, temArgs); err != nil {
			log.Println(err, "template execution error.")
			http.Error(w, "template execution error", http.StatusBadRequest)
			return
		}
	}
}

func isImgFileName(fileName string) bool {
	for _, suffix := range _SUPPORTED_IMAGE_FILE_SUFFIXES {
		if strings.HasSuffix(fileName, suffix) {
			return true
		}
	}
	return false
}

func parseTemplateText(templateName string, templateText string) *template.Template {
	return template.Must(template.New(templateName).Parse(templateText))
}

// 下面拷贝自 net/http/fs.go

func containsDotDot(v string) bool {
	if !strings.Contains(v, "..") {
		return false
	}
	for _, ent := range strings.FieldsFunc(v, isSlashRune) {
		if ent == ".." {
			return true
		}
	}
	return false
}

func isSlashRune(r rune) bool { return r == '/' || r == '\\' }
